package top.techqi.usbd.driver.serial

import android.hardware.usb.UsbConstants
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbInterface
import top.techqi.core.kext.hhByte_T
import top.techqi.core.kext.hlByte_T
import top.techqi.core.kext.lhByte_T
import top.techqi.core.kext.llByte_T
import top.techqi.usbd.UsbDefine
import top.techqi.usbd.UsbDefine.ReqType
import top.techqi.usbd.desc.IADDesc
import top.techqi.usbd.util.DescInflater
import java.io.IOException
import java.util.EnumSet

class CdcAcmSerialDriver(device: UsbDevice, intfId: Int) : BasicsSerialDriver(device, intfId) {
    private lateinit var cInterface: UsbInterface
    private lateinit var dInterface: UsbInterface

    private lateinit var cEndpoint: UsbEndpoint

    override val supportedControlLines: EnumSet<ControlLine> =
        EnumSet.of(ControlLine.DTR, ControlLine.RTS)

    override var dtr: Boolean = false
        set(value) {
            field = value
            setModemCtrl()
        }

    override var rts: Boolean = false
        set(value) {
            field = value
            setModemCtrl()
        }

    override fun openInterfaces() {
        val descriptors = DescInflater.inflate(conn)
        val iad = descriptors.filterIsInstance<IADDesc>().find {
            it.bFirstInterface <= intfId && intfId < it.bFirstInterface + it.bInterfaceCount
        } ?: throw IOException("No IAD descriptor found for interface: $intfId")

        val intfRange = iad.bFirstInterface..<iad.bFirstInterface + iad.bInterfaceCount

        val intfCount = device.interfaceCount
        for (index in 0..<intfCount) {
            val intf = device.getInterface(index)

            if (intf.id !in intfRange) {
                continue
            } else if (intf.interfaceClass == UsbDefine.INTF_CLASS.CDC_COMM
                && intf.interfaceSubclass == UsbDefine.INTF_SUBCLASS.CDM_ACM
            ) {
                cInterface = intf
                cEndpoint = intf.getEndpoint(0)
                conn.claimInterface(cInterface, true)
            } else if (intf.interfaceClass == UsbDefine.INTF_CLASS.CDC_DATA) {
                dInterface = intf
                for (i in 0 until dInterface.endpointCount) {
                    val ep = dInterface.getEndpoint(i)
                    if (ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                        if (ep.direction == UsbConstants.USB_DIR_IN) {
                            rEndpoint = ep
                        } else {
                            wEndpoint = ep
                        }
                    }
                }
                conn.claimInterface(dInterface, true)
            }
        }
    }

    override fun closeInterfaces() {
        conn.releaseInterface(cInterface)
        conn.releaseInterface(dInterface)
    }

    override fun setParameters(baudRate: Int, dataBits: Byte, stopBits: Byte, parity: Byte) {
        if (baudRate <= 0) {
            throw IllegalArgumentException("Invalid baud rate: $baudRate")
        }
        if (dataBits < DATABITS_5 || dataBits > DATABITS_8) {
            throw IllegalArgumentException("Invalid data bits: $dataBits")
        }

        val message = ByteArray(7)
        message[0] = baudRate.llByte_T
        message[1] = baudRate.lhByte_T
        message[2] = baudRate.hlByte_T
        message[3] = baudRate.hhByte_T
        message[6] = dataBits

        message[4] = when (stopBits) {
            STOPBITS_1 -> 0
            STOPBITS_2 -> 2
            else -> throw IllegalArgumentException("Invalid stop bits: $stopBits")
        }
        message[5] = when (parity) {
            PARITY_NONE -> 0
            PARITY_ODD -> 1
            PARITY_EVEN -> 2
            PARITY_MARK -> 3
            PARITY_SPACE -> 4
            else -> throw IllegalArgumentException("Invalid parity: $parity")
        }

        controlTx(SET_LINE_CODING, 0, message)
    }

    private fun controlRx(request: Int, value: Int, buffer: ByteArray): Int {
        return conn.controlTransfer(
            ReqType.GET or ReqType.CLS or ReqType.INT,
            request, value, cInterface.id, buffer, buffer.size, DEFAULT_TIMEOUT_RW
        )
    }

    private fun controlTx(request: Int, value: Int, buffer: ByteArray?): Int {
        return conn.controlTransfer(
            ReqType.SET or ReqType.CLS or ReqType.INT,
            request, value, cInterface.id, buffer, buffer?.size ?: 0,
            DEFAULT_TIMEOUT_RW
        )
    }

    @Throws(IOException::class)
    private fun setModemCtrl() {
        val value = (if (rts) 0x2 else 0) or (if (dtr) 0x1 else 0)

        if (controlTx(SET_CONTROL_LINE_STATE, value, null) < 0) {
            throw IOException("Error setting DTR/RTS byte")
        }
    }

    companion object {
        private const val DEFAULT_TIMEOUT_RW = 5000

        // USB CDC 1.1 section 6.2
        const val SET_LINE_CODING: Int = 0x20
        const val GET_LINE_CODING: Int = 0x21
        const val SET_CONTROL_LINE_STATE = 0x22
    }
}
